package com.xcity.game.activity2.groupbuy;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

import com.alibaba.fastjson.JSON;
import com.xcity.Server;
import com.xcity.config.ConfigEx;
import com.xcity.db.entity.Activity;
import com.xcity.db.entity.Player;
import com.xcity.game.activity2.ActivityImpl;
import com.xcity.game.activity2.groupbuy.model.ActivityData;
import com.xcity.game.activity2.groupbuy.model.BoughtRecord;
import com.xcity.game.activity2.groupbuy.model.GroupBuyData;
import com.xcity.game.activity2.groupbuy.template.GroupBuyConfig;
import com.xcity.game.activity2.groupbuy.template.GroupBuyScoreRewardTemplate;
import com.xcity.game.activity2.groupbuy.template.GroupBuyTemplate;
import com.xcity.game.activity2.groupbuy.template.GroupBuyTemplate.Discount;
import com.xcity.game.common.Cause;
import com.xcity.game.common.MessageConst;
import com.xcity.game.event.GameEvents;
import com.xcity.game.item.listener.DefaultBagListener;
import com.xcity.game.player.PlayerService;
import com.xcity.pomelo.PomeloRoute;
import com.xcity.pomelo.push.msg.ActivityStatePush;
import com.xcity.pomelo.push.msg.BatchItemsPush;
import com.xcity.pomelo.request.ActivityGroupBuyInfoRequest;
import com.xcity.pomelo.request.ActivityGroupBuyRequest;
import com.xcity.pomelo.request.ActivityGroupBuyRewardInfoRequest;
import com.xcity.pomelo.request.ActivityGroupRewardRequest;
import com.xcity.pomelo.response.ActivityGroupBuyInfoResponse;
import com.xcity.pomelo.response.ActivityGroupBuyInfoResponse.GroupBuyItem;
import com.xcity.pomelo.response.ActivityGroupBuyResponse;
import com.xcity.pomelo.response.ActivityGroupBuyRewardInfoResponse;
import com.xcity.pomelo.response.ActivityGroupBuyRewardInfoResponse.GroupBuyRewardItem;
import com.xcity.pomelo.response.ActivityGroupBuyRewardResponse;
import com.xcity.pomelo.response.HintResponse;

import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import naga.x.App;
import naga.x.event.Event;
import naga.x.event.EventListener;
import naga.x.game.template.TemplateService;
import naga.x.game.time.DayListener;
import naga.x.game.time.TimeUpdater;
import naga.x.net.Packet;
import naga.x.net.Session;
import naga.x.net.annotation.OPHandler;
import naga.x.net.impl.pomelo.annotation.PomeloOP;
import naga.x.service.ServiceException;
import naga.x.util.IOUtils;
import naga.x.util.TimeUtils;

/**
 * 团购活动
 * @author yang.li
 *
 */
@OPHandler
public class GroupBuyActivity extends ActivityImpl implements EventListener, DayListener {

	private List<GroupBuyTemplate> goods;
	private TIntObjectMap<GroupBuyTemplate> goodsMap;
	private List<GroupBuyScoreRewardTemplate> rewardTemplates;
	
	private ActivityData activityData;
	private Queue<Long> firstBuyQueue; // 当天第一次购买的玩家
	private AtomicBoolean settling; // 是否正在结算（凌晨结算期间不允许购买，避免并发出问题）
	
	private String buyEndTimeText, rewardEndTimeText; // for client
	
	public static final int QUEUE_FLUSH_SIZE = 128;
	
	public GroupBuyActivity(Activity entity) {
		super(entity);
//		firstBuyQueue = new TLongArrayBlockingQueue();
		firstBuyQueue = new LinkedBlockingQueue<>();
		settling = new AtomicBoolean();
	}

	@Override
	public String getId() {
		return getClass().getName();
	}

	@Override
	public void init() {
		TemplateService service = App.getApp().getServiceManager().get(TemplateService.class);
		goods = service.getTemplatesList(GroupBuyTemplate.class);
		goodsMap = service.getTemplates(GroupBuyTemplate.class);
		rewardTemplates = service.getTemplatesList(GroupBuyScoreRewardTemplate.class);
		activityData = getEntity().getDataObj(ActivityData.class);
		buyEndTimeText = TimeUtils.format("yyyy年MM月dd日", activityData.buyEndTime);
		rewardEndTimeText = TimeUtils.format("yyyy年MM月dd日", getEntity().getEndTime());
		GroupBuyUtils.activitySerial = activityData.serial;
	}

	@Override
	public void save() {
		GroupBuyUtils.flushBuyQueue(firstBuyQueue);
		getEntity().setData(JSON.toJSONString(activityData));
	}

	@Override
	public void startup() throws ServiceException {
		App.getApp().getEventManager().register(this);
		TimeUpdater.getInstance().removeDayListener(this); // make sure add once
		TimeUpdater.getInstance().addDayListener(this);
		Server.broadcast(new ActivityStatePush(entity.getUniqueId(), Activity.STATE_OPENED));
	}

	@Override
	public void shutdown() throws ServiceException {
		GroupBuyUtils.flushBuyQueue(firstBuyQueue);
		App.getApp().getEventManager().unRegister(this);
		Server.broadcast(new ActivityStatePush(entity.getUniqueId(), Activity.STATE_CLOSED));
	}

	@Override
	public boolean update() {
		if (firstBuyQueue.size() >= QUEUE_FLUSH_SIZE) {
			GroupBuyUtils.flushBuyQueue(firstBuyQueue);
		}
		return true;
	}

	@Override
	public void setDirty() {
		ActivityData preData = activityData;
		init();
		if (preData != null && preData.serial == activityData.serial) {
			// 活动序号未变，则不清空历史记录
			activityData.soldMap = preData.soldMap;
			return;
		}
		IOUtils.deleteFile(new File(((ConfigEx) App.getApp().getConfig()).localDbDir, "groupBuy.db"));
		Collection<Player> players = App.getApp().getServiceManager().get(PlayerService.class).getOnlinePlayers();
		for (Player player : players) {
			GroupBuyUtils.clear(player, activityData.serial);
		}
	}
	
	@Override
	public int[] getEventTypes() {
		return new int[] { GameEvents.EVENT_PLAYER_LOADED };
	}

	@Override
	public void handleEvent(Event event) {
		playerLoaded(event.getParameter(0));
	}
	
	private void playerLoaded(Player player) {
		if (!settling.get()) {
			GroupBuyUtils.clear(player, activityData.serial);
		}
		player.send(new ActivityStatePush(entity.getUniqueId(), Activity.STATE_OPENED));
	}
	
	@Override
	public boolean dayChanged() {
		App.getApp().getThreadPool().execute(new Runnable() {
			@Override
			public void run() {
				dayChanged0();
			}
		});
		return buyable();
	}
	
	private void dayChanged0() {
		try {
			settling.set(true);
			Queue<Long> queue = firstBuyQueue;
			if (!queue.isEmpty()) {
				firstBuyQueue = new LinkedBlockingQueue<>(); 
				GroupBuyUtils.flushBuyQueue(queue);
			}
			TIntObjectMap<Discount> discounts = new TIntObjectHashMap<>();
			for (int i = 0; i < goods.size(); i++) {
				GroupBuyTemplate template = goods.get(i);
				int soldNum = activityData.getSoldNum(template.getId());
				// 该商品最终折扣
				Discount discount = template.getDiscount(soldNum);
				discounts.put(template.getId(), discount);
				LOG.info("[GROUPBUY FINAL DISCOUNT]ID[{}]SOLD[{}]DISCOUNT[{}]PRICE[{}]", template.getId(), soldNum, discount.discount, discount.price);
			}
			GroupBuyUtils.doReturnStone(LOG, discounts);
//			activityData.clear();
		} finally {
			settling.set(false);
		}
	}

	@PomeloOP(PomeloRoute.ACTIVITY_GROUP_BUY_INFO)
	protected void requestInfo(Session session, ActivityGroupBuyInfoRequest req) {
		Player player = session.getClient();
		if (!settling.get()) {
			GroupBuyUtils.clear(player, activityData.serial);
		}
		GroupBuyData data = GroupBuyUtils.get(player, false);
		ActivityGroupBuyInfoResponse res = new ActivityGroupBuyInfoResponse(req.getSerial());
		res.endTime = buyEndTimeText;
		res.rewardEndTime = rewardEndTimeText;
		if (data != null) {
			res.score = data.score;
			res.coupon = data.coupon;
		}
		int size = goods.size();
		res.goods = new ArrayList<>(size);
		for (int i = 0; i < size; i++) {
			GroupBuyTemplate template = goods.get(i);
			GroupBuyItem item = new GroupBuyItem();
			item.id = template.getId();
			item.itemId = template.getItemId();
			item.count = template.getCount();
			item.couponLimit = template.getCouponLimit();
			item.numLimit = template.getNumLimit();
			if (data != null) {
//				BoughtRecord br = data.getRecord(template.getId());
//				if (br != null) {
//					item.couponUsed = br.couponUsed;
//					item.numLimit = template.getNumLimit() - br.boughtNum;
					item.numLimit = template.getNumLimit() - data.sizeOf(template.getId());
					if (item.numLimit < 0) item.numLimit = 0;
//				}
			}
			item.soldNum = activityData.getSoldNum(template.getId());
			Discount d = template.getDiscount(item.soldNum);
			item.price = d.price;
			item.discount = d.discount;
			item.fullPrice = template.getPrice();
			item.discounts = template.getDiscountsWithoutFullPrice();
			res.goods.add(item);
		}
		session.send(res);
	}
	
	@PomeloOP(PomeloRoute.ACTIVITY_GROUP_BUY)
	protected void requestBuy(Session session, ActivityGroupBuyRequest req) {
		if (!buyable()) {
			session.send(new HintResponse(req.getSerial(), MessageConst.ACTIVITY_ENDED));
			return;
		}
		if (settling.get()) {
			session.send(new HintResponse(req.getSerial(), MessageConst.ACTIVITY_GROUP_BUY_SETTLING));
			return;
		}
		GroupBuyTemplate template = goodsMap.get(req.id);
		if (template == null) {
			session.send(new HintResponse(req.getSerial(), MessageConst.SHOP_ITEM_NOT_EXIST));
			return;
		}
		Player player = session.getClient();
		int soldNum = activityData.getSoldNum(template.getId());
		GroupBuyUtils.clear(player, activityData.serial);
		GroupBuyData data = GroupBuyUtils.get(player, false);
//		BoughtRecord br = data != null ? data.getRecord(template.getId()) : null;
		int boughtNum = data != null ? data.sizeOf(template.getId()) : 0;
//		if (br != null && br.boughtNum >= template.getNumLimit()) {
		if (boughtNum >= template.getNumLimit()) {
			session.send(new HintResponse(req.getSerial(), MessageConst.SHOP_BUY_NUM_TODAY_MAXED));
			return;
		}
		if (player.getBag().isFull()) {
			session.send(new HintResponse(req.getSerial(), MessageConst.BAG_FULL));
			return;
		}
		Discount discount = template.getDiscount(soldNum);
		int price = discount.price;
		// 确定可使用团购券数量
		int couponUsed = data != null ? data.coupon : 0;
		if (couponUsed > 0) {
			int n = template.getCouponLimit();
			if (n < couponUsed) {
				couponUsed = n;
			}
			n = (price + GroupBuyConfig.couponRatio - 1) / GroupBuyConfig.couponRatio;
			if (n < couponUsed) {
				// 可使用的券数量超过该商品价格等价的券数
				couponUsed = n;
				price = 0;
			} else {
				price -= couponUsed * GroupBuyConfig.couponRatio;
			}
			GroupBuyUtils.decCoupon(player, data, couponUsed, Cause.GROUP_BUY);
		}
		if (price > 0 && !player.costStone(price, Cause.GROUP_BUY)) {
			session.send(new HintResponse(req.getSerial(), MessageConst.STONE_NOT_ENOUTH));
			return;
		}
		DefaultBagListener lsr = new DefaultBagListener();
		player.getBag().addItemWithoutException(template.getItemId(), template.getCount(), Cause.GROUP_BUY, lsr);
		BoughtRecord br = GroupBuyUtils.fireBought(player, template.getId(), couponUsed, price);
		activityData.fireSold(template.getId());
		if (data == null) {
			data = GroupBuyUtils.get(player, false);
		}
		if (data.records.size() == 1) { // 当天第一次购买
			firstBuyQueue.offer(player.getId());
		}
		LOG.info("[GROUPBUY]ID[{}]GOODS[{}]ITEM[{}]DISCOUNT[{}]PRICE[{}]COUPON[{}]STONE[{}]",
				player.getId(), template.getId(), template.getItemId(), discount.discount, discount.price, couponUsed, price);
		
		soldNum = activityData.getSoldNum(template.getId());
		discount = template.getDiscount(soldNum);
		ActivityGroupBuyResponse res = new ActivityGroupBuyResponse(req.getSerial());
		res.id = template.getId();
		res.price = discount.price;
//		res.couponUsed = br.couponUsed;
//		res.numLimit = template.getNumLimit() - br.boughtNum;
		res.numLimit = template.getNumLimit() - data.sizeOf(template.getId());
		if (res.numLimit < 0) res.numLimit = 0;
		res.soldNum = soldNum;
		res.discount = discount.discount;
		res.score = data.score;
		res.coupon = data.coupon;
		session.send(res);
		Packet sync = lsr.getPacket() != null ? lsr.getPacket() : new BatchItemsPush(template.getItemId(), template.getCount());
		session.send(sync);
	}
	
	private boolean buyable() {
		long t = TimeUpdater.getInstance().now();
		return t >= entity.getBeginTime().getTime() && t <= activityData.buyEndTime;
	}
	
	@PomeloOP(PomeloRoute.ACTIVITY_GROUP_BUY_REWARD_INFO)
	protected void requestRewardInfo(Session session, ActivityGroupBuyRewardInfoRequest req) {
		Player player = session.getClient();
		if (!settling.get()) {
			GroupBuyUtils.clear(player, activityData.serial);
		}
		GroupBuyData data = GroupBuyUtils.get(player, false);
		int state = data != null ? data.rewardState : 0;
		ActivityGroupBuyRewardInfoResponse res = new ActivityGroupBuyRewardInfoResponse(req.getSerial());
		res.score = data != null ? data.score : 0;
		res.items = new ArrayList<>(rewardTemplates.size());
		for (int i = 0; i < rewardTemplates.size(); i++) {
			GroupBuyScoreRewardTemplate template = rewardTemplates.get(i);
			GroupBuyRewardItem item = new GroupBuyRewardItem();
			item.id = i; // use the index as the id
			item.itemId = template.getItemId();
			item.count = template.getCount();
			item.score = template.getScore();
			int s = state >> i & 1;
			if (s == 0) {
				item.state = (byte) (data != null && data.score >= item.score ? 1 : 0);
			} else {
				item.state = 2; // 已领取
			}
			res.items.add(item);
		}
		session.send(res);
	}
	
	@PomeloOP(PomeloRoute.ACTIVITY_GROUP_BUY_REWARD)
	protected void requestReward(Session session, ActivityGroupRewardRequest req) {
		if (!isActive()) {
			session.send(new HintResponse(req.getSerial(), MessageConst.ACTIVITY_ENDED));
			return;
		}
		if (settling.get()) {
			session.send(new HintResponse(req.getSerial(), MessageConst.ACTIVITY_GROUP_BUY_SETTLING));
			return;
		}
		if (req.id < 0 || req.id >= rewardTemplates.size()) {
			session.send(new HintResponse(req.getSerial(), MessageConst.SCORE_NOT_ENOUGH));
			return;
		}
		Player player = session.getClient();
		GroupBuyUtils.clear(player, activityData.serial);
		GroupBuyData data = GroupBuyUtils.get(player, false);
		if (data == null) {
			session.send(new HintResponse(req.getSerial(), MessageConst.SCORE_NOT_ENOUGH));
			return;
		}
		if ((data.rewardState >> req.id & 1) == 1) {
			session.send(new HintResponse(req.getSerial(), MessageConst.REWARD_ALREADY_RECEIVED));
			return;
		}
		GroupBuyScoreRewardTemplate template = rewardTemplates.get(req.id);
		if (data.score < template.getScore()) {
			session.send(new HintResponse(req.getSerial(), MessageConst.SCORE_NOT_ENOUGH));
			return;
		}
		data.rewardState |= 1 << req.id;
		DefaultBagListener lsr = new DefaultBagListener();
		player.getBag().addItemWithoutException(template.getItemId(), template.getCount(), Cause.GROUP_BUY_REWARD, lsr);
		ActivityGroupBuyRewardResponse res = new ActivityGroupBuyRewardResponse(req.getSerial());
		res.id = req.id;
		res.state = 2;
		res.coupon = data.coupon;
		session.send(res);
		Packet sync = lsr.getPacket() != null ? lsr.getPacket() : new BatchItemsPush(template.getItemId(), template.getCount());
		session.send(sync);
		LOG.info("[GROUPBUY REWARD]ID[{}]INDEX[{}]ITEM[{}]SCORE[{}]SCORENEED[{}]",
				player.getId(), req.id, template.getItemId(), data.score, template.getScore());
	}
	
//	private boolean rewardable() {
//		long t = TimeUpdater.getInstance().now();
//		return t >= entity.getBeginTime().getTime() && t <= entity.getEndTime().getTime();
//	}
	

}
